/**************************************************
 * Filename: Object2Excel.java
 * Version: v1.0
 * CreatedDate: 2011-4-30
 * Copyright (C) 2011 By cafebabe.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 *
 * If you would like to negotiate alternate licensing terms, you may do
 * so by contacting the author: talentyao@foxmail.com
 ***************************************************/
 
package tk.mystudio.idc.excel;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jxl.Workbook;
import jxl.read.biff.BiffException;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import tk.mystudio.idc.excel.ExcelDesc.Title;

/**
 * 功能描述
 * @author cafebabe
 * @since 2011-4-30 
 * @version 1.0
 */
public class Object2Excel<E> {
	
	/** 存放sheet名称列名与字段名的对应关系及名称信息*/
	private ExcelDesc excelDesc;
	
	Class<E> clazz;

	/**
	 * 构造函数 
	 */
	public Object2Excel(Class<E> clazz) {
		this.clazz = clazz;
		parseObject(clazz);
	}


	/**
	 * 根据@Hearder注解提取对象信息构建列名与字段名的对应
	 * @param obj 需要解析的对象
	 */
	private void parseObject(Class<E> clazz) {
		excelDesc = new ExcelDesc();
		List<ExcelDesc.Title> aTitles = new ArrayList<ExcelDesc.Title>(); // 存放注解字段
		List<ExcelDesc.Title> titles = new ArrayList<ExcelDesc.Title>();  // 存放没有注解的字段
		Sheet st = clazz.getAnnotation(Sheet.class);
		if (st != null) {
			excelDesc.setSheet(st.value());
		}
		Field[] fields= clazz.getDeclaredFields();
		for (Field field : fields) {
			boolean flag = field.isAnnotationPresent(Header.class);
			field.setAccessible(true);
			String fName = field.getName();
			ExcelDesc.Title EDTitle = new ExcelDesc.Title();
			EDTitle.setfName(fName);
			if (flag) { // 添加带注解的字段
				Header header = field.getAnnotation(Header.class);
				EDTitle.setaName(header.value());
				if (!header.format().equals(Format.class)) {
					EDTitle.setFormat(header.format());
					EDTitle.setPattern(header.pattern());
				} else {
					EDTitle.setFormat(null);
					EDTitle.setPattern(null);
				}
				aTitles.add(EDTitle);
			} else {
				titles.add(EDTitle);				
			}
		}
		// 若没有任何@Header注解字段
		if (aTitles.isEmpty()) {
			excelDesc.setTitles(titles);
		} else {			
			excelDesc.setTitles(aTitles);
		}		
	}

	
	/**
	 *  将任意对象的字段和值转换为Map<fieldName,fieldValue>形式,并返回该Map
	 * @param obj 需要转换的对象
	 * @return 该类对象字段有@Header注解的字段,否则返回全部字段.
	 * @throws Exception
	 */
	private Map<String, Object> ObjectField2Map(E obj) throws Exception {
		Map<String, Object> aMap = new HashMap<String, Object>(); // 存放解析后的注解字段
		Map<String, Object> map = new HashMap<String, Object>(); // 存放解析后的非注解字段
		Field[] fields= obj.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			String fName = field.getName();
			Object value = field.get(obj);
			boolean flag = field.isAnnotationPresent(Header.class);			
			if (flag) {
				aMap.put(fName, value);
			} else { // 无注解字段
				map.put(fName, value);
			}
		}
		if (aMap.isEmpty()) { 
			return map;
		}
		return aMap;
	}
	
	/**
	 * 将一个list对象导出成一个Excel 
	 * @param srcs 待导出的原数据
	 * @param filename 导出的文件名
	 * @return 导出的文件
	 * @throws Exception
	 */
	public File Export(List<E> srcs, String filename) throws Exception  {
		File excel = new File(filename);
		WritableWorkbook workbook = Workbook.createWorkbook(excel);
		WritableSheet sheet = createSheet(workbook);
		setTitle(sheet);
		for (E obj : srcs) {
			setRow(ObjectField2Map(obj), sheet);
		}
		workbook.write();
		workbook.close();
		return excel;
	}
	
	/**
	 * 功能描述 
	 * @param sheet 
	 * @throws Exception
	 * @throws
	 */
	private void setTitle(WritableSheet sheet) throws Exception {
		List<Title> titles = excelDesc.getTitles();
		for (int col=0; col<titles.size(); col++) {
			ExcelDesc.Title title = titles.get(col);
			String titleName =  title.getaName();
			if (titleName == null || titleName.isEmpty()) {
				titleName = title.getfName();
			}
			jxl.write.Label label = new jxl.write.Label(col, 0, titleName);
			sheet.addCell(label);
		}
	}


	/**
	 * 将Excel文件转为指定对象的集合
	 * @param file
	 * @param clazz
	 * @return
	 * @throws IOException 
	 * @throws BiffException 
	 */
	public List<E> Import(InputStream is) throws Exception {
		List<E> lists = new ArrayList<E>();
		Workbook  workbook = Workbook.getWorkbook(is);
        jxl.Sheet sheet = workbook.getSheet(0);
        int rows = sheet.getRows();
        String[] titleName = getTitle(sheet);
        for (int row=1; row<rows; row++) {
        	Map<String, String> rowValue = ExcelRow2Map(row, titleName, sheet);
        	E obj = createObject(rowValue);
        	lists.add(obj);
        }
		return lists;
	}
	
	/**
	 * 功能描述
	 * @param rowValue
	 * @return 
	 * @throws Exception 
	 */
	private E createObject(Map<String, String> rowValue) throws Exception {
		E obj = clazz.newInstance();
		Field[] fields = obj.getClass().getDeclaredFields();
		for (Field field : fields) {
			boolean flag = field.isAnnotationPresent(Header.class);
			field.setAccessible(true);
			if (flag) {
				Object newValue = convert(rowValue, field);
				field.set(obj,newValue);
			} else {
				
			}
		}
			
		return obj;
	}


	/**
	 * 根据字段类型给字段赋值
	 * @param rowValue 待转换的String值
	 * @param field 待赋值的字段
	 * @return 赋值后的对象
	 * @throws ParseException 
	 */
	private Object convert(Map<String, String> rowValue, Field field)
			throws ParseException {
		Header hd = field.getAnnotation(Header.class);
		String key = hd.value();
		String value = rowValue.get(key);
		if (value == null) value ="0";
		Object newValue = "";
		Class<?> type = field.getType();				
		if ((Date.class).equals(type)) {
			switch (hd.format()) {
			case SimpleDateFormat:
				SimpleDateFormat sdf = new SimpleDateFormat(hd.pattern());
				try {
					newValue = sdf.parse(value);
				} catch (ParseException e) { // 二次尝试
					sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");
					newValue = sdf.parseObject(value);
				}
				break;
			case MessageFormat:
			case DecimalFormat:
				break;
			}
		} else if ((Byte.class).equals(type) || (byte.class).equals(type)) {
			newValue = Byte.parseByte(value);
		} else if ((Short.class).equals(type) || (short.class).equals(type)) {
			newValue = Short.parseShort(value);
		}else if ((Integer.class).equals(type) || (int.class).equals(type)) {
			newValue = Integer.parseInt(value);
		} else if ((Long.class).equals(type) || (long.class).equals(type)) {
			newValue = Long.parseLong(value);
		} else if ((Double.class).equals(type) || (double.class).equals(type)) {
			newValue = Double.parseDouble(value);
		} else if ((Float.class).equals(type) || (float.class).equals(type)) {
			newValue = Float.parseFloat(value);
		} else if ((String.class).equals(type)) {
			newValue = rowValue.get(key);
		} else {
			System.out.println("unknown type:" + type);
		}
		return newValue;
	}


	/**
	 * 解析Excel指定行的值并转为Map<String, String>
	 * @param row
	 * @param titleName
	 * @param sheet
	 * @return 
	 */
	private Map<String, String> ExcelRow2Map(int row, String[] titleName,
			jxl.Sheet sheet) {
		Map<String, String> rowValue = new HashMap<String, String>();
		for (int i=0; i<titleName.length; i++) {
			String key = titleName[i];
			String value = sheet.getCell(i, row).getContents();
			rowValue.put(key, value);
		}
		return rowValue;
	}


	/**
	 * 功能描述
	 * @param sheet
	 * @return 
	 */
	private String[] getTitle(jxl.Sheet sheet) {
		int cols = sheet.getColumns();
		String[] colName = new String[cols];
		for (int i=0; i<cols; i++) {
			colName[i] = sheet.getCell(i, 0).getContents();
		}
		return colName;
	}


	/**
	 * 见解析出的字段Map填充到单元格中
	 * @param Mfield 字段Map
	 * @param sheet 待填充的sheet	
	 * @throws Exception 
	 */
	private void setRow(Map<String, Object> Mfield,	WritableSheet sheet) throws Exception {
		int row = sheet.getRows();
		List<Title> titles = excelDesc.getTitles();
		for (int col=0; col<titles.size(); col++) {
			
			Title title = titles.get(col);
			Object value = Mfield.get(title.getfName());
			String cellValue = "";
			if (value != null) {
				cellValue = FormatUtil(value, title.getFormat(), title.getPattern());
			}
			jxl.write.Label label = new jxl.write.Label(col, row, cellValue);
			sheet.addCell(label);			
		}
		
	}


	/***
	 * 
	 * 功能描述
	 * @param workbook
	 * @return
	 */
	private WritableSheet createSheet(WritableWorkbook workbook) {
		int number = workbook.getNumberOfSheets();
		WritableSheet sheet;
		String sheetName = excelDesc.getSheet();
		if (sheetName != null && !sheetName.isEmpty()) {
			sheet = workbook.createSheet(sheetName.trim(), number);
		} else {
			sheet = workbook.createSheet("sheet"+number, number);
		}
		return sheet;
	}
	
	public OutputStream Export(List<E> srcs) {
		return null;
	}


	/**
	 * 按指定格式器格式化字段值
	 * @param obj 待格式化的字段
	 * @param format 格式器
	 * @param pattern 模式
	 * @return
	 * @throws Exception 
	 */
	private static String FormatUtil(Object obj, Header.Format format,
			String pattern) throws Exception {
		// 第二方案
		String ans = obj.toString();
		switch (format) {
		case SimpleDateFormat:
			if (obj instanceof Date) {
				SimpleDateFormat sdf = new SimpleDateFormat(pattern);
				ans = sdf.format((Date) obj);
			}
			break;
		case MessageFormat:
			ans = MessageFormat.format(pattern, obj);
			break;
		case DecimalFormat:
			ans = new DecimalFormat(pattern).format(obj);
			break;
		}
		return ans;
	}
	
	
}

